{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 8.1命令式和符号式混合编程(动态图和静态图)\n",
    "\n",
    "本书到目前为止一直都在使用命令式编程(动态图)，它使用编程语句改变程序状态。考虑下面这段简单的命令式程序。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def add(a, b):\n",
    "    return a + b\n",
    "\n",
    "def fancy_func(a, b, c, d):\n",
    "    e = add(a, b)\n",
    "    f = add(c, d)\n",
    "    g = add(e, f)\n",
    "    return g\n",
    "\n",
    "fancy_func(1, 2, 3, 4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "和我们预期的一样，在运行语句`e = add(a, b)`时，Python会做加法运算并将结果存储在变量`e`中，从而令程序的状态发生改变。类似地，后面的两条语句`f = add(c, d)`和`g = add(e, f)`会依次做加法运算并存储变量。\n",
    "\n",
    "虽然使用命令式编程很方便，但它的运行可能很慢。一方面，即使`fancy_func`函数中的`add`是被重复调用的函数，Python也会逐一执行这3条函数调用语句。另一方面，我们需要保存变量`e`和`f`的值直到`fancy_func`中所有语句执行结束。这是因为在执行`e = add(a, b)`和`f = add(c, d)`这2条语句之后我们并不知道变量`e`和`f`是否会被程序的其他部分使用。\n",
    "\n",
    "与命令式编程不同，符号式编程通常在计算流程完全定义好后才被执行。多个深度学习框架，如Theano和TensorFlow，都使用了符号式编程。通常，符号式编程的程序需要下面3个步骤：\n",
    "\n",
    "1. 定义计算流程；\n",
    "2. 把计算流程编译成可执行的程序；\n",
    "3. 给定输入，调用编译好的程序执行。\n",
    "\n",
    "下面我们用符号式编程重新实现本节开头给出的命令式编程代码。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "def add(a, b):\n",
      "    return a + b\n",
      "\n",
      "def fancy_func(a, b, c, d):\n",
      "    e = add(a, b)\n",
      "    f = add(c, d)\n",
      "    g = add(e, f)\n",
      "    return g\n",
      "\n",
      "print(fancy_func(1, 2, 3, 4))\n",
      "\n",
      "10\n"
     ]
    }
   ],
   "source": [
    "def add_str():\n",
    "    return '''\n",
    "def add(a, b):\n",
    "    return a + b\n",
    "'''\n",
    "\n",
    "def fancy_func_str():\n",
    "    return '''\n",
    "def fancy_func(a, b, c, d):\n",
    "    e = add(a, b)\n",
    "    f = add(c, d)\n",
    "    g = add(e, f)\n",
    "    return g\n",
    "'''\n",
    "\n",
    "def evoke_str():\n",
    "    return add_str() + fancy_func_str() + '''\n",
    "print(fancy_func(1, 2, 3, 4))\n",
    "'''\n",
    "\n",
    "prog = evoke_str()\n",
    "print(prog)\n",
    "y = compile(prog, '', 'exec')\n",
    "exec(y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "以上定义的3个函数都仅以字符串的形式返回计算流程。最后，我们通过`compile`函数编译完整的计算流程并运行。由于在编译时系统能够完整地获取整个程序，因此有更多空间优化计算。例如，编译的时候可以将程序改写成`print((1 + 2) + (3 + 4))`，甚至直接改写成`print(10)`。这样不仅减少了函数调用，还节省了内存。\n",
    "\n",
    "对比这两种编程方式，我们可以看到以下两点。\n",
    "\n",
    "* 命令式编程更方便。当我们在Python里使用命令式编程时，大部分代码编写起来都很直观。同时，命令式编程更容易调试。这是因为我们可以很方便地获取并打印所有的中间变量值，或者使用Python的调试工具。\n",
    "\n",
    "* 符号式编程更高效并更容易移植。一方面，在编译的时候系统容易做更多优化；另一方面，符号式编程可以将程序变成一个与Python无关的格式，从而可以使程序在非Python环境下运行，以避开Python解释器的性能问题。\n",
    "\n",
    "\n",
    "## 混合式编程取两者之长\n",
    "\n",
    "大部分深度学习框架在命令式编程和符号式编程之间二选一。例如，Theano和受其启发的后来者TensorFlow使用了符号式编程，Chainer和它的追随者PyTorch使用了命令式编程。开发人员在设计飞桨时思考了这个问题：有没有可能既得到命令式编程的好处，又享受符号式编程的优势？开发者们认为，用户应该用纯命令式编程进行开发和调试；当需要产品级别的计算性能和部署时，用户可以将大部分命令式程序转换成符号式程序来运行。飞桨将命令式编程称为动态图模式，称符号式编程为静态图模式。\n",
    "\n",
    "动态图有诸多优点，包括易用的接口，Python风格的编程体验，友好的debug交互机制等。在动态图模式下，代码是按照我们编写的顺序依次执行。这种机制更符合Python程序员的习 惯，可以很方便地将大脑中的想法快速地转化为实际代码，也更容易调试。但在性能方面， Python执行开销较大，与C++有一定差距。因此在工业界的许多部署场景中（如大型推荐系统、移动端）都倾向于直接使用C++来提速。\n",
    "\n",
    "相比动态图，静态图在部署方面更具有性能的优势。静态图程序在编译执行时，先搭建模型 的神经网络结构，然后再对神经网络执行计算操作。预先搭建好的神经网络可以脱离Python依赖，在C++端被重新解析执行，而且拥有整体网络结构也能进行一些网络结构的优化。\n",
    "\n",
    "动态图代码更易编写和debug，但在部署性能上，静态图更具优势。因此我们新增了动态图转静态图的功能，支持用户依然使用动态图编写组网代码。PaddlePaddle会对用户代码进行 分析，自动转换为静态图网络结构，兼顾了动态图易用性和静态图部署性能两方面优势。\n",
    "\n",
    "飞桨主要的动转静方式是基于源代码级别转换的ProgramTranslator。其基本原理是通过分析Python代码来将动态图代码转写为静态图代码，并在底层自动帮用户使用静态图执行器运行。这种转换方式使得用户可以灵活使用Python语法及其控制流来构建神经网络模型。除此之外，PaddlePaddle另外提供一种基于trace的动转静接口TracedLayer。若遇到ProgramTranslator不支持但是可以用TracedLayer运行的情况，可以作为备选方案。\n",
    "\n",
    "## 基于源代码转写的ProgramTranslator\n",
    "\n",
    "源代码转写的ProgramTranslator进行动态图转静态图，其基本原理是通过分析Python代码来将动态图代码转写为静态图代码，并在底层自动帮用户使用执行器运行。其基本使用方法十分简便，只需要在要转化的函数（该函数也可以是用户自定义动态图Layer的forward函数）前添加一个装饰器 @paddle.jit.to_static ，一个转化例子如下，可以直接运行被装饰函数得到结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2,1] sahpe[0] is 2 , output is Tensor(shape=[2], dtype=int32, place=CPUPlace, stop_gradient=True,\n",
      "       [4, 2])\n",
      "[2] sahpe[0] is 1 , output is Tensor(shape=[1], dtype=float64, place=CPUPlace, stop_gradient=True,\n",
      "       [-4.])\n"
     ]
    }
   ],
   "source": [
    "import paddle\n",
    "import numpy as np\n",
    "@paddle.jit.to_static\n",
    "def func(input_var):\n",
    "    # if判断与输入input_var的shape有关\n",
    "    if input_var.shape[0] > 1:\n",
    "        out = paddle.cast(input_var, \"int32\")\n",
    "        out = paddle.add(out, out)\n",
    "    else:\n",
    "        out = paddle.cast(input_var, \"float64\")\n",
    "        out = paddle.add(-out, -out)\n",
    "    return out\n",
    "\n",
    "in_np = np.array([2,1]).astype('float64')\n",
    "input_var = paddle.to_tensor(in_np)\n",
    "output=func(input_var)\n",
    "print(f\"[2,1] sahpe[0] is {in_np.shape[0]} , output is {output}\")\n",
    "\n",
    "in_np = np.array([2]).astype('int')\n",
    "input_var = paddle.to_tensor(in_np)\n",
    "output=func(input_var)\n",
    "print(f\"[2] sahpe[0] is {in_np.shape[0]} , output is {output}\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "若要存储转化后的静态图模型，可以调用 paddle.jit.save ，我们定义一个简单全连接网络SimpleFcLayer，需要在下面SimpleFcLayer的forward函数添加装饰器："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import paddle\n",
    "\n",
    "class SimpleFcLayer(paddle.nn.Layer):\n",
    "    def __init__(self, batch_size, feature_size, fc_size):\n",
    "        super(SimpleFcLayer, self).__init__()\n",
    "        self._linear = paddle.nn.Linear(feature_size, fc_size)\n",
    "        self._offset = paddle.to_tensor(\n",
    "            np.random.random((batch_size, fc_size)).astype('float32'))\n",
    "\n",
    "    @paddle.jit.to_static\n",
    "    def forward(self, x):\n",
    "        fc = self._linear(x)\n",
    "        return fc + self._offset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "存储该模型可以使用 paddle.jit.save 接口："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import paddle\n",
    "\n",
    "fc_layer = SimpleFcLayer(3, 4, 2)\n",
    "in_np = np.random.random([3, 4]).astype('float32')\n",
    "input_var = paddle.to_tensor(in_np)\n",
    "out = fc_layer(input_var)\n",
    "\n",
    "paddle.jit.save(fc_layer, \"./fc_layer_dy2stat\", input_spec=[input_var])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 基于trace的TracedLayer\n",
    "\n",
    "trace是指在模型运行时记录下其运行过哪些算子。TracedLayer就是基于这种技术，在一次执行动态图的过程中，记录所有运行的算子，并构建和保存静态图模型。一个使用例子如下：\n",
    "\n",
    "我们还是定义一个简单的全连接网络作为例子，注意这里不需要像ProgramTranslator在forward函数添加装饰器："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import paddle\n",
    "\n",
    "class SimpleFcLayer(paddle.nn.Layer):\n",
    "    def __init__(self, batch_size, feature_size, fc_size):\n",
    "        super(SimpleFcLayer, self).__init__()\n",
    "        self._linear = paddle.nn.Linear(feature_size, fc_size)\n",
    "        self._offset = paddle.to_tensor(\n",
    "            np.random.random((batch_size, fc_size)).astype('float32'))\n",
    "\n",
    "    def forward(self, x):\n",
    "        fc = self._linear(x)\n",
    "        return fc + self._offset\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "接下来是TracedLayer如何存储模型："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "import paddle\n",
    "from paddle.jit import TracedLayer\n",
    "\n",
    "fc_layer = SimpleFcLayer(3, 4, 2)\n",
    "in_np = np.random.random([3, 4]).astype('float32')\n",
    "# 将numpy的ndarray类型的数据转换为Tensor类型\n",
    "input_var = paddle.to_tensor(in_np)\n",
    "# 通过 TracerLayer.trace 接口将命令式模型转换为声明式模型\n",
    "out_dygraph, static_layer = TracedLayer.trace(fc_layer, inputs=[input_var])\n",
    "save_dirname = './saved_infer_model'\n",
    "# 将转换后的模型保存\n",
    "static_layer.save_inference_model(save_dirname, feed=[0], fetch=[0])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0.77430046 2.1039274 ]\n",
      " [0.601015   1.4656237 ]\n",
      " [1.0648551  0.9460722 ]]\n"
     ]
    }
   ],
   "source": [
    "import paddle\n",
    "paddle.enable_static() #paddle.static.Executor() 只能在静态图下使用\n",
    "exe = paddle.static.Executor()\n",
    "program, feed_vars, fetch_vars = paddle.static.load_inference_model(save_dirname, exe)\n",
    "fetch, = exe.run(program, feed={feed_vars[0]: in_np}, fetch_list=fetch_vars)\n",
    "print(fetch)\n",
    "exe.close() #关闭执行器\n",
    "\n",
    "paddle.disable_static() #再切换回动态图，以免影响后面的程序"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "但是也正如我们阐述的原理，trace只是记录了一次执行涉及的算子。若在用户的模型代码中，包含了依赖数据条件（包括输入的值或者shape）的控制流分支，即根据数据条件触发运行不同的算子，则TracedLayer无法正常工作。\n",
    "\n",
    "## 比较ProgramTranslator和TracedLayer\n",
    "\n",
    "基于源代码转换的ProgramTranslator对比基于trace的TracedLayer，前者能够处理依赖数据条件的控制流分支。因此我们更推荐用户使用ProgramTranslator，如果遇到问题再以TracedLayer作为备选方案。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 内部架构原理\n",
    "\n",
    "TracedLayer的原理就是trace，相对简单，因此我们在这里不展开描述。本节将主要阐述ProgramTranslator基于源代码将动态图代码转化为静态图代码。\n",
    "\n",
    "转化过程发生在用户开始调用被装饰的函数，转换过程在装饰器中实现。我们将内部涉及的过程分为以下几步：\n",
    "\n",
    "## 函数与缓存\n",
    "动态图转静态图的主体是函数（Function）。对于函数内包含的PaddlePaddle接口，如果是仅计算相关算子代码语句，那么因为PaddlePaddle动态图和静态图接口一致，我们不需要额外转换这些代码为静态图代码。但是对于动态图，此类代码接口是直接运行计算和返回结果，而对于静态图此类代码接口其实是组网。那么如果被转化的函数被调用多次，动态图转静态图后会多次组网添加对应算子，这显然会导致问题。为了解决这个问题以及为了加速动转静转化过程，我们维护了被装饰器装饰的函数（Function）与其输入形状（shape），数据类型（dtype）映射到被转化后组网的Program的缓存（Cache）。当要被转化的函数命中缓存，我们直接用对应存储的Program运行静态图得到结果，否则我们才进行语句转化，并且转化成功后的Program存储进缓存。\n",
    "\n",
    "## 动态图源码转AST（抽象语法树）\n",
    "动态图转静态图的最核心部分类似一个编译器，解析动态图代码语句为AST，再对应AST进行改写，最后反转回成静态图代码。从函数转化为代码字符串可以使用Python的inspect.getsource。从字符串Python提供了自带的 ast 库来解析字符串为AST，但是由于Python2，Python3的语法略有不同，为了避免我们需要额外处理这些Python2，Python3的不同情况，我们使用了统一Python2，Python3的开源AST处理 gast库 。这些接口使得函数转化为AST没有本质上的困难。\n",
    "\n",
    "## AST改写和静态图源码转换\n",
    "这部分为动转静最核心的部分，我们对支持的各种语法进行ast转写。其中最重要的Python控制流，if-else，while，for循环被分别分析转化为PaddlePaddle静态图接口cond，while_loop等接口实现。我们对想转化的每一种主要语法创建一个Transformer（这里的Transformer是Python ast转写的概念，而不是自然语言处理NLP领域的Transformer），每个Transformer扫一遍AST并进行对应的改写。最后被转化完成的AST我们使用gast提供的接口转回成源码。\n",
    "\n",
    "## 静态图源码作为动态图一部分运行的技术\n",
    "为了动静转化更加易用和被转化的代码能在动态图中复用，我们在拥有源码后运行生成Program，并将这个Program作为一个大op，包装成动态图的一个op，这样既能把用户的代码转为静态图提速或者保存部署，另一方面如果用户想在Python层使用生成的静态图代码作为动态图的一部分继续训练或者别的动态图运算也是可以直接使用。\n",
    "\n",
    "## 易用性与Debug功能在动转静过程的实现\n",
    "正如AST转写类似编译器，而一般编译器都会提供debug断点，报错，输出一些中间代码等功能。我们在进行动转静时，万一用户的动态图代码出错，或者用户想断点调试，或者用户想看看被转化后的静态图代码是否符合其预期，我们也希望能够像编译器一样提供这些易用性功能，使得动转静兼顾性能和部署同时还具有易用性。我们这里将列出这些功能的实现方式\n",
    "\n",
    "* 报错对应到动态图代码行。由于被转化后的静态图代码和原动态图代码不同，Python运行出错时会报静态图的错误，因此我们在每一次AST转写时添加AST节点对应的原动态图代码行等信息，在Python报错栈中将静态图的报错转化成对应的动态图源码报错\n",
    " \n",
    "* 设置断点功能。我们保留了被转化后代码的中的pdb.set_trace(), 用户可以使用这种方式进行断点调试\n",
    " \n",
    "* 查看最后转化的静态图代码。我们输出为一个StaticLayer class，这个StaticLayer可以直接被调用，但是也存储转化后的代码，可以调用StaticLayer.code来获得转化后的代码。\n",
    " \n",
    "* 输出中间转化状态代码，甚至不同语法Transformer转化的代码，比如经过for循环转化后代码是什么样的。我们开放接口设定了log level来让用户可以打印中间状态转化的代码。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 支持语法列表\n",
    "ProgramTranslator本质是把Python运行语法转写为PaddlePaddle静态图代码，但是Python语法的表达能力和PaddlePaddle静态图表达能力存在不同，这使得一些代码无法被转换。\n",
    "\n",
    "本节我们将详细讲述在动转静过程中支持转化哪些语法，不支持哪些语法，并且讲述如何改写代码能够解决语法不支持的场景。\n",
    "\n",
    "动转静支持的语法分为以下几个大类：\n",
    "\n",
    "## 控制流相关关键词\n",
    "控制流指if-elif-else，while等能够控制程序语句执行顺序的关键字。PaddlePaddle静态图通过cond，while_loop API来实现条件判断和循环，如果动态图Python控制流的判断条件或循环条件依赖 PaddlePaddle Tensor，动转静后会被转化为等价的PaddlePaddle控制流接口，否则仍然使用Python控制流逻辑运行。在动转静过程中这些关键字的转化情况为：\n",
    "\n",
    "1. if-elif-else 条件\n",
    "\n",
    "当 if <条件> 中的条件是Tensor时，ProgramTranslator会把该if-elif-else语句转化为等价的cond API语句。否则会按普通Python if-elif-else的逻辑运行。需注意cond支持的Tensor只能是numel为1的bool Tensor，所以请使用这种Tensor进行条件判断，其他Tensor会报错。\n",
    "\n",
    "2. while 循环\n",
    "\n",
    "当while循环中的条件是Tensor时，ProgramTranslator会把该while语句转化为等价的while_loop API语句，否则会按普通Python while运行。需注意while循环条件中的Tensor只能是numel为1的bool Tensor，所以请使用这种Tensor进行条件判断，其他Tensor会报错。\n",
    "\n",
    "3. for 循环\n",
    "\n",
    "3.1 for _ in range(__) 循环\n",
    "\n",
    "ProgramTranslator先将其转化为等价的Python while循环，然后按while循环的逻辑进行动静转换。\n",
    "\n",
    "3.2 for _ in x 循环\n",
    "\n",
    "当x是Python容器或迭代器，则会用普通Python逻辑运行。当x是Tensor时，会转化为循环中每次对应拿出x[0], x[1], ... 。\n",
    "\n",
    "3.3 for idx, val in enumerate(x) 循环\n",
    "\n",
    "当x是Python容器或迭代器，则会用普通Python逻辑运行。当x是Tensor时，idx会转化为依次0，1，...的1-D Tensor。val会转化为循环中每次对应拿出x[0], x[1], ... 。\n",
    "\n",
    "4. break，continue\n",
    "\n",
    "ProgramTranslator 可以支持在循环中添加break，continue语句，其底层实现原理是对于要break，continue的部分在相应时候使用cond在一定条件下跳过执行。\n",
    "\n",
    "5. return\n",
    "\n",
    "ProgramTranslator 支持在循环，条件判断中return结果而不需要一定在函数末尾return。也能够支持return不同长度tuple和不同类型的Tensor。其底层实现原理是对return后的部分相应使用cond在一定条件下跳过执行。\n",
    "\n",
    "## 一些需要转化的运算类型\n",
    "1. +，-，，/，*, >, <, >= , <=, == 等Python内置运算\n",
    "\n",
    "由于静态图有重载这些基本运算符，所以这些被ProgramTranslator转化后都适用相应重载的运算符，动转静支持此类运算。\n",
    "\n",
    "2. and，or，not 逻辑运算\n",
    "\n",
    "Python内置and，or，not逻辑运算关键词，ProgramTranslator在语句的运算时会判断逻辑运算关键词运行的对象是否是Tensor，如果都是Tensor，我们将其转化为静态图对应的逻辑运算接口并运行。\n",
    "\n",
    "3. 类型转化\n",
    "\n",
    "动态图中可以直接用Python的类型转化语法来转化Tensor类型。例如x是Tensor时，float(x)可以将x的类型转化为float。ProgramTranslator在运行时判断x是否是Tensor，如果是，则在动转静时使用静态图cast接口转化相应的Tensor类型。\n",
    "\n",
    "## Python 函数相关\n",
    "1. print\n",
    "\n",
    "如果x是Tensor，在动态图模式中print(x)可以打印x的值。在动转静过程中我们把此转化为静态图的Print接口实现，使得在静态图中也能打印。如果print的参数不是Tensor，那么我们没有把相应print语句进行转写。\n",
    "\n",
    "2. len\n",
    "\n",
    "如果x是Tensor，在动态图模式中len(x)可以获得x第0维度的长度。在动转静中我们把此转化为静态图shape接口，并返回shape的第0维。另外如果x是个TensorArray，那么len(x)将会使用静态图接口control_flow.array_length返回TensorArray的长度。对于其他情况，动转静时会按照普通Python len函数运行。\n",
    "\n",
    "3. lambda 表达式\n",
    "\n",
    "动转静允许写带有Python lambda表达式的语句，并且我们会适当改写使得返回对应结果。\n",
    "\n",
    "4. 函数内再调用函数\n",
    "\n",
    "对于函数内调用其他函数的情况，ProgramTranslator也会对内部的函数递归地进行动转静，这样做的好处是可以在最外层函数只需加一次装饰器即可，而不需要每个函数都加装饰器。但需要注意，动转静还不支持函数递归调用自己，详细原因请查看下文动转静无法正确运行的情况。\n",
    "\n",
    "## 报错异常相关\n",
    "assert\n",
    "\n",
    "如果x是Tensor，在动态图中可以通过assert x来强制x为True或者非0值，在动转静中我们把此转化为静态图Assert接口支持此功能。\n",
    "\n",
    "## Python基本容器\n",
    "1. list：对于一个list如果里面元素都是Tensor，那么动转静会转化其为TensorArray，静态图TensorArray可以支持append，pop，修改操作。因此ProgramTranslator在元素皆为Tensor的list中支持上面三种操作。换言之，其他list操作，比如sort无法支持。对于list中并非所有元素是Tensor的情况，ProgramTranslator会将其作为普通Python list运行。\n",
    "\n",
    "2. dict：ProgramTranslator会将相应的dict中的Tensor添加进静态图Program，因此使用dict是动转静支持的语法。\n",
    "\n",
    "## 动转静无法正确运行的情况\n",
    "1. Reshape后的变量调用其shape作为PaddlePaddle API参数。\n",
    "\n",
    "具体表现比如 x = reshape(x, shape=shape_tensor) ，再使用 x.shape[0] 的值进行其他操作。这种情况会由于动态图和静态图的本质不同而使得动态图能够运行，但静态图运行失败。其原因是动态图情况下，API是直接返回运行结果，因此 x.shape 在经过reshape运算后是确定的。但是在转化为静态图后，因为静态图API只是组网，shape_tensor 的值在组网时是不知道的，所以 reshape 接口组网完，静态图并不知道 x.shape 的值。PaddlePaddle静态图用-1表示未知的shape值，此时 x 的shape每个维度会被设为-1，而不是期望的值。\n",
    "\n",
    "遇到这类情况我们建议尽量固定shape值，减少reshape操作。\n",
    "\n",
    "2. 多重list嵌套读写Tensor\n",
    "\n",
    "具体表现如 l = [[tensor1, tensor2], [tensor3, tensor4]] ，因为现在动转静将元素全是Tensor的list转化为TensorArray，而PaddlePaddle的TensorArray还不支持多维数组，因此这种情况下，动转静无法正确运行。\n",
    "\n",
    "遇到这类情况我们建议尽量用一维list，或者自己使用PaddlePaddle的create_array，array_read，array_write接口编写为TensorArray。\n",
    "\n",
    "3. Tensor值在被装饰函数中转成numpy array进行运算\n",
    "\n",
    "具体表现为在被装饰函数中没有返回Tensor时就使用 numpy.array(tensor) 将Tensor转化为numpy array并使用numpy接口进行运算。这种情况在动态图下因为Tensor有值是可以正常运行的，但是在静态图时由于Tensor只是组网变量，在没有运行时没有数值，因此无法进行numpy运算。\n",
    "\n",
    "遇到这种情况我们建议在动转静的函数中尽量使用PaddlePaddle接口替代numpy接口进行运算。\n",
    "\n",
    "4. 一个函数递归调用自己\n",
    "\n",
    "ProgramTranslator还无法支持一个函数递归调用自己，原因是递归常常会用 if-else 构造停止递归的条件。然而这样的停止条件在静态图下只是一个 cond 组网，组网并不能在编译阶段决定自己组多少次，会导致函数运行时一直组网递归直至栈溢出，因此ProgramTranslator还无法支持一个函数递归调用自己。\n",
    "\n",
    "遇到这种情况我们建议将代码改为非递归写法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#InputSpec 功能介绍\n",
    "在飞桨框架中，可以通过 paddle.jit.to_static 装饰普通函数或 Layer 的最外层 forward 函数，将动态图模型转换为静态图执行。但在动转静时，需要给模型传入 Tensor 数据并执行一次前向，以保证正确地推导出网络中各 Tensor 的 shape 。此转换流程需要显式地执行一次动态图函数，增加了接口使用的成本；同时，传入实际 Tensor 数据则无法定制化模型输入的shape，如指定某些维度为 None 。\n",
    "\n",
    "因此，飞桨提供了 InputSpec 接口，可以更加便捷地执行动转静功能，以及定制化输入 Tensor 的 shape 、name 等信息。\n",
    "\n",
    "## 一、InputSpec 对象构造方法\n",
    "\n",
    "1.1 直接构造 InputSpec 对象\n",
    "InputSpec 接口在 paddle.static 目录下，用于描述一个 Tensor 的签名信息：shape、dtype、name。使用样例如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "InputSpec(shape=(-1, 784), dtype=VarType.FP32, name=x)\n",
      "InputSpec(shape=(-1, 1), dtype=VarType.INT64, name=label)\n"
     ]
    }
   ],
   "source": [
    "from paddle.static import InputSpec\n",
    "\n",
    "x = InputSpec([None, 784], 'float32', 'x')\n",
    "label = InputSpec([None, 1], 'int64', 'label')\n",
    "\n",
    "print(x)      # InputSpec(shape=(-1, 784), dtype=VarType.FP32, name=x)\n",
    "print(label)  # InputSpec(shape=(-1, 1), dtype=VarType.INT64, name=label)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1.2 根据 Tensor 构造 InputSpec 对象\n",
    "\n",
    "可以借助 InputSpec.from_tensor 方法，从一个 Tensor 直接创建 InputSpec 对象，其拥有与源 Tensor 相同的 shape 和 dtype 。使用样例如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "InputSpec(shape=(2, 2), dtype=VarType.FP32, name=x)\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import paddle\n",
    "from paddle.static import InputSpec\n",
    "\n",
    "paddle.disable_static()\n",
    "\n",
    "x = paddle.to_tensor(np.ones([2, 2], np.float32))\n",
    "x_spec = InputSpec.from_tensor(x, name='x')\n",
    "print(x_spec)  # InputSpec(shape=(2, 2), dtype=VarType.FP32, name=x)\n",
    "#注：若未在 from_tensor 中指定新的name，则默认使用与源Tensor相同的name。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1.3 根据 numpy.ndarray 构造 InputSpec 对象\n",
    "\n",
    "也可以借助 InputSpec.from_numpy 方法，从一个 Numpy.ndarray 直接创建 InputSpec 对象，其拥有与源 ndarray 相同的 shape 和 dtype 。使用样例如下：\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "InputSpec(shape=(2, 2), dtype=VarType.FP32, name=x)\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "from paddle.static import InputSpec\n",
    "\n",
    "x = np.ones([2, 2], np.float32)\n",
    "x_spec = InputSpec.from_numpy(x, name='x')\n",
    "print(x_spec)  # InputSpec(shape=(2, 2), dtype=VarType.FP32, name=x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 二、基本使用方法\n",
    "动转静 paddle.jit.to_static 装饰器支持 input_spec 参数，用于指定被装饰函数每个 Tensor 类型输入参数的 shape 、 dtype 、 name 等签名信息。不必再显式地传入 Tensor 数据以触发网络层 shape 的推导。 Paddle 会解析 to_static 中指定的 input_spec 参数，构建网络的起始输入，进行后续的模型组网。\n",
    "\n",
    "同时，借助 input_spec 参数，可以自定义输入 Tensor 的 shape ，比如指定 shape 为 [None, 784] ，其中 None 表示变长的维度。\n",
    "\n",
    "2.1 to_static 装饰器模式\n",
    "\n",
    "如下是一个简单的使用样例："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/layers/utils.py:77: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working\n",
      "  return (isinstance(seq, collections.Sequence) and\n"
     ]
    }
   ],
   "source": [
    "import paddle\n",
    "from paddle.jit import to_static\n",
    "from paddle.static import InputSpec\n",
    "from paddle.fluid.dygraph import Layer\n",
    "\n",
    "class SimpleNet(Layer):\n",
    "    def __init__(self):\n",
    "        super(SimpleNet, self).__init__()\n",
    "        self.linear = paddle.nn.Linear(10, 3)\n",
    "\n",
    "    @to_static(input_spec=[InputSpec(shape=[None, 10], name='x'), InputSpec(shape=[3], name='y')])\n",
    "    def forward(self, x, y):\n",
    "        out = self.linear(x)\n",
    "        out = out + y\n",
    "        return out\n",
    "\n",
    "net = SimpleNet()\n",
    "\n",
    "# save static model for inference directly\n",
    "paddle.jit.save(net, './simple_net')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在上述的样例中， to_static 装饰器中的 input_spec 为一个 InputSpec 对象组成的列表，用于依次指定参数 x 和 y 对应的 Tensor 签名信息。在实例化 SimpleNet 后，可以直接调用 paddle.jit.save 保存静态图模型，不需要执行任何其他的代码。\n",
    "\n",
    "注解\n",
    "\n",
    "input_spec 参数中只支持 InputSpec 对象，暂不支持如 int 、 float 等类型。\n",
    "\n",
    "若指定 input_spec 参数，则需为被装饰函数的所有必选参数都添加对应的 InputSpec 对象，如上述样例中，不支持仅指定 x 的签名信息。\n",
    "\n",
    "若被装饰函数中包括非 Tensor 参数，且指定了 input_spec ，请确保函数的非 Tensor 参数都有默认值，如 forward(self, x, use_bn=False)\n",
    "\n",
    "2.2 to_static函数调用\n",
    "\n",
    "若期望在动态图下训练模型，在训练完成后保存预测模型，并指定预测时需要的签名信息，则可以选择在保存模型时，直接调用 to_static 函数。使用样例如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 0, batch_id: 0, loss is: [40.777607], acc is: [0.109375]\n",
      "epoch: 0, batch_id: 400, loss is: [0.16774216], acc is: [0.96875]\n",
      "epoch: 0, batch_id: 800, loss is: [0.06042239], acc is: [0.96875]\n"
     ]
    }
   ],
   "source": [
    "import paddle \n",
    "from paddle.jit import to_static\n",
    "from paddle.static import InputSpec\n",
    "\n",
    "train_dataset = paddle.vision.datasets.MNIST(mode='train')\n",
    "test_dataset = paddle.vision.datasets.MNIST(mode='test')\n",
    "lenet = paddle.vision.models.LeNet()\n",
    "\n",
    "# 加载训练集 batch_size 设为 64\n",
    "batch_size =64\n",
    "train_loader = paddle.io.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n",
    "\n",
    "def train():\n",
    "    epochs = 1\n",
    "    adam = paddle.optimizer.Adam(learning_rate=0.001, parameters=lenet.parameters())\n",
    "    # 用Adam作为优化函数\n",
    "    for epoch in range(epochs):\n",
    "        for batch_id, data in enumerate(train_loader()):\n",
    "            x_data, y_data = data\n",
    "            predicts = lenet(x_data)\n",
    "            loss = paddle.nn.functional.cross_entropy(predicts, y_data, reduction='mean')\n",
    "            acc = paddle.metric.accuracy(predicts, y_data, k=1)\n",
    "            avg_acc = paddle.mean(acc)\n",
    "            loss.backward()\n",
    "            if batch_id % 400 == 0:\n",
    "                print(\"epoch: {}, batch_id: {}, loss is: {}, acc is: {}\".format(epoch, batch_id, loss.numpy(), avg_acc.numpy()))\n",
    "            adam.step()\n",
    "            adam.clear_grad()\n",
    "# 启动训练\n",
    "train()\n",
    "\n",
    "\n",
    "net = to_static(lenet, input_spec=[InputSpec(shape=[batch_size, 1, 28, 28], name='x')]) #手写数字识别每个字的shape是[1, 28, 28]\n",
    "\n",
    "# save static model for inference directly\n",
    "paddle.jit.save(net, './simple_net')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 调试方法\n",
    "本节内容将介绍动态图转静态图（下文简称：动转静）推荐的几种调试方法。\n",
    "\n",
    "请确保转换前的动态图代码能够成功运行，建议使用 paddle.jit.ProgramTranslator().enable(False)关闭动转静功能，直接运行动态图，如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2020-11-18 00:00:59,605 Dynamic-to-Static WARNING: The decorator '@paddle.jit.to_static' does NOT work when setting ProgramTranslator.enable to False. We will just return dygraph output. If you would like to get static graph output, please call API ProgramTranslator.enable(True)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tensor(shape=[3, 2], dtype=float64, place=CPUPlace, stop_gradient=True,\n",
      "       [[2., 2.],\n",
      "        [2., 2.],\n",
      "        [2., 2.]])\n"
     ]
    }
   ],
   "source": [
    "import paddle\n",
    "import numpy as np\n",
    "# 关闭动转静动能\n",
    "paddle.jit.ProgramTranslator().enable(False)\n",
    "@paddle.jit.to_static\n",
    "def func(x):\n",
    "    x = paddle.to_tensor(x)\n",
    "    if x[0][0] > 0:\n",
    "        x = x + 1\n",
    "    return x\n",
    "\n",
    "print(func(np.ones([3,2])))\n",
    "paddle.jit.ProgramTranslator().enable(True)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 断点调试\n",
    "使用动转静功能时，您可以使用断点调试代码。 例如，在代码中，调用 pdb.set_trace()："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pdb\n",
    "\n",
    "@paddle.jit.to_static\n",
    "def func(x):\n",
    "    x = paddle.to_tensor(x)\n",
    "    pdb.set_trace()\n",
    "    if x[0][0] > 3:\n",
    "        x = x - 1\n",
    "    return x\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2020-11-18 00:01:15,472 Dynamic-to-Static INFO: After the last level ast transformer: 'All Transformers', the transformed code:\n",
      "def func(x):\n",
      "    x = paddle.nn.functional.assign(x)\n",
      "    pdb.set_trace()\n",
      "\n",
      "    def true_fn_2(x):\n",
      "        x = x - 1\n",
      "        return x\n",
      "\n",
      "    def false_fn_2(x):\n",
      "        return x\n",
      "    x = paddle.jit.dy2static.convert_ifelse(x[0][0] > 3, true_fn_2,\n",
      "        false_fn_2, (x,), (x,), (x,))\n",
      "    return x\n",
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "> /tmp/tmpl7mfppnr.py(7)func()\n",
      "-> def true_fn_2(x):\n",
      "(Pdb) > /tmp/tmpl7mfppnr.py(11)func()\n",
      "-> def false_fn_2(x):\n",
      "(Pdb) "
     ]
    },
    {
     "data": {
      "text/plain": [
       "Tensor(shape=[3, 2], dtype=float64, place=CPUPlace, stop_gradient=True,\n",
       "       [[1., 1.],\n",
       "        [1., 1.],\n",
       "        [1., 1.]])"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#执行以下代码，将会在转化后的静态图代码中使用调试器：\n",
    "func(np.ones([3, 2]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 打印转换后的代码\n",
    "您可以打印转换后的静态图代码，有2种方法：\n",
    "\n",
    "1. 使用被装饰后的函数的 code 属性 如下代码中，装饰器 paddle.jit.to_static 会将函数 func 转化为一个类对象 StaticLayer，可以使用 StaticLayer 的 code 属性来获得转化后的代码。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "def func(x):\n",
      "    x = paddle.nn.functional.assign(x)\n",
      "\n",
      "    def true_fn_0(x):\n",
      "        x = x - 1\n",
      "        return x\n",
      "\n",
      "    def false_fn_0(x):\n",
      "        return x\n",
      "    x = paddle.jit.dy2static.convert_ifelse(x > 3, true_fn_0, false_fn_0, (\n",
      "        x,), (x,), (x,))\n",
      "    return x\n",
      "\n"
     ]
    }
   ],
   "source": [
    "@paddle.jit.to_static\n",
    "def func(x):\n",
    "    x = paddle.to_tensor(x)\n",
    "    if x > 3:\n",
    "        x = x - 1\n",
    "    return x\n",
    "\n",
    "print(func.code)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用 set_code_level(level=100, also_to_stdout=False) 或环境变量 TRANSLATOR_CODE_LEVEL=level\n",
    "\n",
    "通过调用 set_code_level 或设置环境变量 TRANSLATOR_CODE_LEVEL，可以在日志中查看转换后的代码：\n",
    "\n",
    "注：测试中在cell输出中看到转换后的代码。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2020-11-18 00:18:16,443 Dynamic-to-Static INFO: After the last level ast transformer: 'All Transformers', the transformed code:\n",
      "def func(x):\n",
      "    x = paddle.nn.functional.assign(x)\n",
      "\n",
      "    def true_fn_13(x):\n",
      "        x = x - 6\n",
      "        return x\n",
      "\n",
      "    def false_fn_13(x):\n",
      "        return x\n",
      "    x = paddle.jit.dy2static.convert_ifelse(x < 2, true_fn_13, false_fn_13,\n",
      "        (x,), (x,), (x,))\n",
      "    return x\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "Tensor(shape=[1], dtype=float64, place=CPUPlace, stop_gradient=True,\n",
       "       [-5.])"
      ]
     },
     "execution_count": 83,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "@paddle.jit.to_static\n",
    "def func(x):\n",
    "    x = paddle.to_tensor(x)\n",
    "    if x < 2:\n",
    "        x = x - 6\n",
    "    return x\n",
    "\n",
    "paddle.jit.set_code_level(level=100, also_to_stdout=False) # 也可设置 os.environ[\"TRANSLATOR_CODE_LEVEL\"] = '100'，效果相同\n",
    "func(np.ones([1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 计算性能\n",
    "由于动态图也进行了细致的优化工作，所以大部分时候动态图并不会比静态图慢多少。\n",
    "\n",
    "# 小结\n",
    "动态图和静态图各有优劣，飞桨可以取二者之长。\n",
    "\n",
    "建议在学习研究的时候用动态图来调试代码，在工业实践领域落地的时候用静态图加快处理速度。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
